
#include "zen_vap_node.h"
#include "zen_log.h"

#define Node Node
namespace Zen { namespace Vap {

	/// Object end

	Node* Node::touchDownNodes(AppTouch const & touch)
	{
		for(auto iter = m_nodes.rbegin(); iter != m_nodes.rend(); ++iter)
		{
			auto res = (*iter)->touchDown(touch);
			if(res) return res;
		}
		return nullptr;
	}
	
	void Node::drawNodes()
	{
		for(auto & i : m_nodes)
		{
			if(i->isVisible()) i->draw();
		}
	}

	void Node::addNode(std::shared_ptr<Node> node)
	{
		musts(node, "add nullptr node");
		musts(node->m_parent.expired(), "node already has a parent");

		m_nodes.push_back(node);

		node->m_parent = shared_from_this();

		m_nodes_dirty = true;

		node->load();
	}

	void Node::addTo(std::shared_ptr<Node> node)
	{
		musts(node, "add node to nullptr");
		musts(m_parent.expired(), "already has a parent");
		this->m_parent = node;
		node->m_nodes_dirty = true;
		node->m_nodes.push_back(shared_from_this());

		this->load();
	}
	void Node::remove()
	{
		/// remove self from parent.
		if(!m_parent.expired())
		{
			auto p = m_parent.lock();
			for(auto i = p->m_nodes.begin(); i != p->m_nodes.end(); ++i)
			{
				if(i->get() == this)
				{
					p->m_nodes.erase(i);
					break;
				}
			}
			m_parent.reset();
		}
		this->stopAllActions();
		this->removeAllNodes();

	}

	void Node::removeAllNodes()
	{
		for(auto i : m_nodes)
		{
			i->m_parent.reset();
			i->remove();
		}
		m_nodes.clear();
	}

	void Node::sortNodes()
	{
		if(!m_nodes_dirty) return;
		auto comp = [](std::shared_ptr<Node> a, std::shared_ptr<Node> b) {
			return a->m_order < b->m_order;
		};
		std::stable_sort(m_nodes.begin(), m_nodes.end(), comp);
		m_nodes_dirty = false;
	}
	/**
	 @ref zen_vap_root.cpp
	 */
	Node::Node()
		{
	}

	Node::~Node()
	{
	}

	void Node::setVisible(bool v)
	{
		m_is_visible = v;
	}
	
	void Node::setOrder(int index)
	{
		if(m_order == index) return;
		m_order = index;
		if(m_parent.use_count())
			m_parent.lock()->m_nodes_dirty = true;
	}


	/// Node2d begin
	Node2d::Node2d()
		{

	}

	/// Node2d end

	/// Node3d begin

	Node3d::Node3d()
		{

	}

	/// Node3d end

	/**
	 Blend
	 */
	DrawStack * DrawStack::Get()
	{
		static auto single = new DrawStack;
		return single;;
	}
	DrawStack::DrawStack()
	{
		this->m_blends.push_back(eBlend::None);
		this->m_mats.push_back(MatrixInfo{nullptr, 0 });
		this->m_alphas.push_back(1.f);
	}
	DrawStack::~DrawStack()
	{
	}
	void DrawStack::pushMatrix(Mat4 const * mat, int version)
	{
		this->m_mats.push_back({mat, version});
	}

	void DrawStack::popMatrix()
	{
		this->m_mats.pop_back();
	}

	void DrawStack::pushAlpha(float a)
	{
		if(!m_alphas.empty()) a *= m_alphas.back();
		this->m_alphas.push_back(a);
	}

	void DrawStack::popAlpha()
	{
		this->m_alphas.pop_back();
	}

	void DrawStack::pushBlend(eBlend blend)
	{
		if(blend == eBlend::Inherit) blend = m_blends.back();
		m_blends.push_back(blend);
	}

	void DrawStack::popBlend()
	{
		m_blends.pop_back();
	}

	void DrawStack::reset()
	{
		this->m_alphas.resize(1);
		this->m_mats.resize(1);
		this->m_blends.resize(1);
	}

	/// matrixing begin

	void PartWithMatrix::setMatrix(const Mat4 &matrix, int parent_version)
	{
		m_final_matrix = matrix;
		m_parent_mat_version = parent_version;
		++m_mat_version;
	}
	bool PartWithMatrix::isMatExpiredFromParent(int parent_version)
	{
		return (parent_version != m_parent_mat_version);
	}

	/// matrix end


	void PartWithMatrix2d::setPosition(float x, float y)
	{
		m_pos.x = x;
		m_pos.y = y;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setX(float x)
	{
		m_pos.x = x;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setY(float y)
	{
		m_pos.y = y;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setScale(float sx, float sy)
	{
		m_scale = { sx, sy };
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setScaleX(float sx)
	{
		m_scale.x = sx;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setScaleY(float sy)
	{
		m_scale.y = sy;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setSkew(float kx, float ky)
	{
		m_skew = { kx, ky };
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setSkewX(float kx)
	{
		m_skew.x = kx;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setSkewY(float ky)
	{
		m_skew.y = ky;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setRotation(float radians)
	{
		m_rotation = radians;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setPosition(Vec2 pos)
	{
		m_pos = pos;
	}
	void PartWithMatrix2d::setScale(Vec2 scale)
	{
		m_scale = scale;
	}
	void PartWithMatrix2d::setSkew(Vec2 skew)
	{
		m_skew = skew;
	}

	void PartWithMatrix2d::setAnchor(float ax, float ay)
	{
		m_anchor = { ax, ay };
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setAnchorX(float ax)
	{
		m_anchor.x = ax;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setAnchorY(float ay)
	{
		m_anchor.y = ay;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setAnchor(Vec2 anchor)
	{
		m_anchor = anchor;
		m_is_matrix_dirty = true;
	}
	void PartWithMatrix2d::setScale2_(float x, float y)
	{
		m_scale2 = { x, y };
		m_is_matrix_dirty = true;
	}
	
	PartWithMatrix2d::PartWithMatrix2d(bool is_anchor_valid)
	{
		m_is_anchor_valid = is_anchor_valid;
	}
	void PartWithMatrix2d::updateMatrix()
	{
		auto rs = DrawStack::Get();
		auto top_mat = rs->getTopMat4();
		auto top_version = rs->getTopMat4Version();
		
		if(m_is_matrix_dirty)
		{
			m_is_matrix_dirty = false;
			
			Mat4 & m = m_matrix0;
			if(!m_is_anchor_valid)
			{
				float cos = cosf(m_rotation);
				float sin = sinf(m_rotation);
				float x = m_scale2.x * m_scale.x;
				float y = m_scale2.y * m_scale.y;

				m[0] = cos * m_skew.x * x;
				m[1] = sin * m_skew.y * x;
				m[4] = -sin * m_skew.x * y;
				m[5] = cos * m_skew.y * y;
				m[12] = m_pos.x;
				m[13] = m_pos.y;
			}
			else {
				float cos = cosf(m_rotation);
				float sin = sinf(m_rotation);
				float x = m_scale2.x * m_scale.x;
				float y = m_scale2.y * m_scale.y;

				m[0] = cos * m_skew.x * x;
				m[1] = sin * m_skew.y * x;
				m[4] = -sin * m_skew.x * y;
				m[5] = cos * m_skew.y * y;
				m[12] = m_pos.x - m[0] * m_anchor.x - m[4] * m_anchor.y;
				m[13] = m_pos.y - m[1] * m_anchor.x - m[5] * m_anchor.y;
			}

			if(top_mat)
			{
				setMatrix(Mat4Multiply(*top_mat, m_matrix0), top_version);
			}
			else
			{
				setMatrix(m_matrix0, 0);
			}
		}
		else if(top_mat && isMatExpiredFromParent(top_version))
		{
			setMatrix(Mat4Multiply(*top_mat, m_matrix0), top_version);
		}
	}

	/// PartWithMatrix3d begin

	PartWithMatrix3d::PartWithMatrix3d()
	{
	}
	void PartWithMatrix3d::updateMatrix() {

		auto rs = DrawStack::Get();
		auto top_mat = rs->getTopMat4();
		auto top_version = rs->getTopMat4Version();

		if(m_is_matrix_dirty)
		{
			m_is_matrix_dirty = false;

			auto m1 = Mat4MakeTranslation(m_view_pos);
			auto m2 = Mat4MakeRotation(m_view_rotation_angle, m_view_rotation_axis);
			auto m3 = Mat4MakeScale(m_view_scale * m_view_size);

			m_matrix0 = Mat4Multiply(Mat4Multiply(m1, m2), m3);

			if(top_mat)
			{
				setMatrix(Mat4Multiply(*top_mat, m_matrix0), top_version);
			}
			else
			{
				setMatrix(m_matrix0, 0);
			}
		}
		else if(top_mat && isMatExpiredFromParent(top_version))
		{
			setMatrix(Mat4Multiply(*top_mat, m_matrix0), top_version);
		}
	}

	void PartWithMatrix3d::setPosition(cVec3 & pos) {
		m_is_matrix_dirty = true;
		m_view_pos = pos;
	}
	void PartWithMatrix3d::setScale  (cVec3 & scale) {
		m_is_matrix_dirty = true;
		m_view_scale = scale;
	}
	void PartWithMatrix3d::setRotationAngle(float rad) {
		m_is_matrix_dirty = true;
		m_view_rotation_angle = rad;
	}

	void PartWithMatrix3d::setRotationAxis(cVec3 & axis) {
		m_is_matrix_dirty = true;
		m_view_rotation_axis = axis;
	}
	void PartWithMatrix3d::setAnchor(cVec3 & anchor) {
		m_is_matrix_dirty = true;
		m_view_anchor = anchor;
	}
	void PartWithMatrix3d::setSize(cVec3 & size) {
		m_is_matrix_dirty = true;
		m_view_size = size;
	}
	/// PartWithMatrix3d end


	void PartWithColor::setColor(Color4f const & color)
	{
		if(m_color == color) return;
		m_color = color;
	}
	void PartWithFlip::setFlipX(bool x)
	{
		if(x == m_is_flip_x) return;
		m_is_flip_x = x;
		m_is_flip_dirty = true;
	}

	void PartWithFlip::setFlipY(bool y)
	{
		if(y == m_is_flip_y) return;
		m_is_flip_y = y;
		m_is_flip_dirty = true;
	}

	bool PartWithFlip::isFlipX()
	{
		return m_is_flip_x;
	}

	bool PartWithFlip::isFlipY()
	{
		return m_is_flip_y;
	}

/// texture

	void PartWithTexture::setTexture(SharedTexture texture)
	{
		m_texture = texture;
		m_is_texture_dirty = true;
	}

	void PartWithTexture::setTextureRect(Zen::Rect4f rect)
	{
		m_texture_rect = rect;
		m_is_texture_dirty = true;
	}

	void PartWithTexture::setTextureScale(Vec2 scale) {
		m_texture_scale = scale;
		m_is_texture_dirty = true;
	}

	bool PartWithTexture::updateTexture_()
	{
		if(m_is_texture_dirty)
		{
			m_texture_size.w = m_texture->size().w * m_texture_rect.w * m_texture_scale.x;
			m_texture_size.h = m_texture->size().h * m_texture_rect.h * m_texture_scale.y;

			m_is_texture_dirty = false;
			return true;
		}
		return false;
	}
	Vec2 PartWithTexture::s_default_texture_scale = { 1.f, 1.f };


}}


